<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>async 函数</title>
</head>

<body>
    <script>
        // ES2017标准 引入了async 函数，使得异步操作更加便利 async 函数
        // async函数对 Generator 函数的改进，体现在以下四点。
        /**
         * 1、内置执行器
         * async 函数自带执行器 调用就执行，不像Generator 调用next() 靠执行器
         * 
         * 2、更好的语义 async 和 await 比起星号和 yield 语义更清楚 async 表示函数里有异步操作，await 表示紧跟在后面的表达式需要等待结果。
         * 
         * 3、更广的实用性 
         * co 模块约定，yield 命令后面只能是Thunk函数或者Promise 对象，而async 函数的 await 命令后面，可以是Promise对象和原始类型的值（数值，字符串和布尔值，但这时会自动转成立即resolved 的 Promise 对象）
         * 4、返回值是Promise
         * async 函数返回值是Promise 对象，这比Generator 函数返回值是 Iterator 对象方便更多了，你可以用then 方法指定下一步操作。
         * 
         * 
         * 由于async函数返回的是 Promise 对象，可以作为await命令的参数。
         */

        function timeout(ms) {
            return new Promise((resolve => {
                // setTimeout 函数 代表的就是 ms 时间之后执行 resolve 
                setTimeout(resolve, ms)
            }))
        }

        async function asyncPrint(value, ms) {
            await timeout(ms)
            console.log(value)
        }
        asyncPrint('都是套路', 50).then(() => {
            console.log("测试一下")
        })

        /**
         * 返回Promise 对象
         * async 函数返回一个Promise对象
         * async 函数内部return 语句返回值 会成为then 方法回调函数的参数
         */
        async function f() {
            return 'hello World'
        }
        f().then(v => console.log(v))

        /*
        async 函数内部抛出错误，会导致返回的Promise 对象变为reject状态
        抛出的错误对象会被catch 方法回调函数接收到
        */
        async function ff() {
            throw new Error('脑子有问题了')
        }
        ff().then(v => console.log("resolve", v), e => console.log('reject', e)).catch(err => console.log('catch::err',
            err))
        ff().then(v => console.log("resolve", v)).catch(err => console.log('catch::err',
            err))
        /*
        Promise 对象的状态变化
        async函数返回的 Promise 对象，必须等到内部所有await命令后面的 Promise 对象执行完，才会发生状态改变，除非遇到return语句或者抛出错误。也就是说，只有async函数内部的异步操作执行完，才会执行then方法指定的回调函数。
        */
        async function getTitle(url) {
            let response = await fetch(url)
            let html = await response.text();
            return html.match(/<title>([\s\S]+)<\/title>/i)[1]
        }
        getTitle('https://tc39').then(r => console.log('r::', r)).catch(err => console.log('err::', err))

        // await 命令
        function sleep(interval) {
            return new Promise(resolve => {
                setTimeout(resolve, interval)
            })
        }
        // 使用
        async function one2fiveInAsync() {
            for (let i = 1; i <= 5; i++) {
                console.log(i)
                await sleep(1000)
            }
            return 'haha'
        }
        one2fiveInAsync().then(val => {
            console.log('one2fiveInAsync::', val)
        })

        // await命令后面的 Promise 对象如果变为reject状态，则reject的参数会被catch方法的回调函数接收到
        async function fff() {
            await Promise.reject('出错了')
        }
        fff()
            .then(v => console.log(v))
            .catch(e => console.log(e))

        // 任何一个await语句后面的 Promise 对象变为reject状态，那么整个async函数都会中断执行。
        async function ffff() {
            await Promise.reject('出错了');
            await Promise.resolve('hello world'); // 不会执行
        }

        // 有时，我们希望即使前一个异步操作失败，也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面，这样不管这个异步操作是否成功，第二个await都会执行。
        async function f2() {
            try {
                await Promise.reject('出错了')
            } catch (e) {
                console.log("ee::", e)
            }
            return await Promise.resolve('hello world f2')
        }
        f2().then(v => console.log(v))
        // 另一种方法是await后面的 Promise 对象再跟一个catch方法，处理前面可能出现的错误。
        async function f3() {
            await Promise.reject('f3 出错了').catch(e => {
                console.log(e)
            });
            return await Promise.resolve('hello world f3')
        }
        f3().then(v => console.log(v))

        // 如果有多个await命令，可以统一放在try...catch结构中。

        function getFoo() {
            console.log('getFoo')
        }

        function getBar() {
            console.log('getBar')
        }

        async function f4() {
            // 多个await 命令后面的异步操作，如果不存在继发关系,最好让他们
            // 同事触发
            // let foo1 = await getFoo()
            // let bar1 = await getBar()
            // 像以上代码 getFoo 和 getBar 是两个独立的异步操作（即互不依赖），被写成继发关系。这样比较耗时，因为只有getFoo 完成以后，才会执行getBar,完全可以让它们同时触发。

            // // 写法一
            // let [foo2, bar2] = await Promise.all([getFoo(), getBar()])

            // // 写法二
            let fooPromise = getFoo()
            let barPromise = getBar()
            let foo3 = await fooPromise;
            let bar3 = await barPromise;
        }
        f4()

        // await 只能用在async 函数 ，但是如果将forEach方法的参数 改成 async 函数，也有问题
        function dbFuc(db) {
            let docs = [{}, {}, {}];

            //可能得到错误结果
            docs.forEach(async function (doc) {
                await db.post(doc)
            })
        }

        // 上面代码可能不会正常工作，原因是这时三个db.post()操作将是并发执行
        // 而不是继发执行 正确的写法应该采用for 循环
        async function dbFun(db) {
            let docs = [{}, {}, {}];
            for (let doc of docs) {
                await db.post(doc)
            }
        }

        // 另一个方法是使用数组reduce 方法
        async function dbFun(db) {
            let docs = [{}, {}, {}]
            await docs.reduce(async (_, doc) => {
                await _;
                await db.post(doc);
            }, undefined)
        }
    </script>
</body>

</html>